from __future__ import absolute_import, unicode_literals
from celery import shared_task
from iClouds.celery import app
from django.conf import settings
from django.db import connections
from django.utils import timezone
from django_redis import get_redis_connection
from iClouds import wePayCrypt as WPC
from iClouds.apiAuthentication import get_tokens_for_user
from iClouds.wechatcfg import wxChatRequest, toMppsModelMessage  # ,wxConf
from iClouds.SDKweApp import wxAppsRequest
from iClouds.apiheader import header_md5_str
from mysqldb.models import AllinpayWxchatList, AllinpayUserUserList, AllinpayRobotList
from mysqldb.models import YlWxOrders
from mysqldb.models import AllinpayRobotReqsn, AllinpayRobotAccount
import time
import json
import logging
import xml.etree.ElementTree as ET

# from dateutil.parser import isoparse
from datetime import datetime

logger = logging.getLogger(__name__)


# @shared_task
# def Test(*args, **kwargs):
#     """测试"""
#     return {"rescode": 200, "resmsg": "HelloWorld"}


# =====================================================
def EncryptedData(db):
    """数据加密"""
    return header_md5_str(f"{db * 5}")


def VerifyData(data: AllinpayRobotAccount):
    """校验账户数据"""
    if (
        EncryptedData(data.bmoney) == data.hmoney
        and EncryptedData(data.btotal) == data.htotal
    ):
        return True
    else:
        return False


def userid_from_wxapp_openid(openid: str):
    """查询小程序用户ID"""
    try:
        r = AllinpayWxchatList.objects.get(appid=openid)
    except BaseException as err:
        return None
    else:
        return r.userid


def openid_from_wxappid(openid: str):
    """查询小程序用户公众号openid"""
    try:
        r = AllinpayWxchatList.objects.get(appid=openid)
    except BaseException as err:
        return None
    else:
        return r.openid


def balance_from_userid(uid: int):
    """查询账户余额"""
    r = (
        AllinpayRobotAccount.objects.filter(
            userid=uid, dbid=settings.ROBOT_MONEY_DEFAULT
        )
        .order_by("id")
        .last()
    )
    if not r:
        return 0, 0, "", ""
    if not VerifyData(r):
        logger.error(f"[用户账户] 用户账户校验失败 {r.id}")
    return r.bmoney, r.btotal, r.hmoney, r.htotal


def balance_from_userai(mode: str):
    """查询价格"""
    if mode == "content":
        return settings.RPBOT_MONEY_QUEST, "订阅文本"  # 文本->订阅文本
    elif mode == "Voice":
        return settings.RPBOT_MONEY_AUDIO_SUBS, "订阅音频"  # 订阅文本->音频
    elif mode == "Mpeg":
        return settings.RPBOT_MONEY_MOVIE_SUBS, "订阅视频"  # 订阅文本->视频
    elif mode == "Text":
        return settings.RPBOT_MONEY_QUEST, "实时文本"  # 文本->文本
    elif mode == "Audio":
        return settings.RPBOT_MONEY_AUDIO, "实时文本合成音频"  # 文本->音频
    elif mode == "Video":
        return settings.RPBOT_MONEY_VIDEO, "实时音频合成视频"  # 音频->视频
    elif mode == "Lipsync":
        return settings.RPBOT_MONEY_LIPSYNC, "Sync唇动视频"  # 音频+视频->视频
    elif mode == "Opencv":
        return settings.RPBOT_MONEY_OPENCV, "Opencv图转视频"  # 音频+视频->视频
    else:
        return 0, "未知类型"


def ainicename_from_uid(uid: int):
    """查询ai昵称"""
    try:
        r = AllinpayRobotList.objects.get(isbot=0, parent=uid)
    except BaseException as err:
        return "未命名"
    else:
        return r.basename


# =====================================================
@app.task()
def myTaskDoTest(*args, **kwargs):
    """测试"""
    x = args[0] + args[1]
    logger.warn(f"[计算结果] {x}")
    return {"rescode": 200, "resmsg": "HelloWorld"}


@shared_task
def wxHasLoginToken(code, state):
    """微信请求登录token"""

    def hasNewUserId(name):
        """创建新用户"""
        r = AllinpayUserUserList(nicename=name, status=0)
        r.save()
        return r.id

    WX = wxChatRequest()
    # 请求Token
    token = WX.hasWebToken(code)
    logger.info(f"[WeCallback] [access_token] {token}")

    # 示例数据
    # {"access_token":"ACCESS_TOKEN",
    # "expires_in":7200,
    # "refresh_token":"REFRESH_TOKEN",
    # "openid":"OPENID",
    # "scope":"SCOPE",
    # "is_snapshotuser": 1,
    # "unionid": "UNIONID"}
    # {"errcode":40029,"errmsg":"invalid code"}
    if token.get("errcode"):
        return token
    # 请求用户数据
    uinfo = {}
    if WX.scope == "snsapi_userinfo":
        uinfo = WX.hasWebUserinfo(token.get("openid"), token.get("access_token"))
        if uinfo.get("errcode"):
            return uinfo
    # 格式化数据
    data = {
        # "unionid": token.get("unionid"),
        "openid": token.get("openid"),
        "nickname": uinfo.get("nickname"),
        "sex": uinfo.get("sex"),
        "province": uinfo.get("province"),
        "city": uinfo.get("city"),
        "country": uinfo.get("country"),
        "headimgurl": uinfo.get("headimgurl"),
        "privilege": uinfo.get("privilege"),
    }
    # 保存用户数据
    unionid = token.pop("unionid")
    res, faq = AllinpayWxchatList.objects.update_or_create(
        defaults=data, unionid=unionid
    )
    if faq:
        res.creattime = timezone.now()
        res.status = 1
        res.userid = hasNewUserId(data["nickname"])
    res.lasttime = timezone.now()
    res.save()

    # 创建Token
    try:
        user = AllinpayUserUserList.objects.get(id=res.userid)
    except BaseException as err:
        return {"errcode": 400, "errmsg": "创建token失败"}
    else:
        # 缓存本地用户Token
        token = get_tokens_for_user(user)["access"]
        rds = get_redis_connection("sysconf")
        rds.set(state, f"Bearer {token}")
        res = rds.expire(state, 180)
    return {"errcode": 200, "errmsg": "扫码登陆成功!"}


# ========================================================
@shared_task
def PHPselectMdb(sql, faq=1, data=None):
    """执行sql语句"""
    with connections["default"].cursor() as cursor:  # 连接MYSQL
        cursor.execute(sql, data)
        if faq == 1:
            result = cursor.fetchone()
        else:
            result = cursor.fetchall()
    return result


# ========================================================
@shared_task
def hasNewUserId(name):
    """创建新用户"""
    r = AllinpayUserUserList(nicename=name, status=1)
    r.save()
    return r.id


@shared_task
def hasWxUserInfo(openid):
    """查询微信用户信息"""
    WX = wxChatRequest()
    token = WX.hasToken()
    if not token:
        return None
    uinfo = WX.hasWxUserinfo(openid, token.get("access_token"))
    return uinfo


@shared_task
def toDict(xml):
    """解析回调数据"""
    try:
        root = ET.fromstring(f"""{xml}""")
    except BaseException as err:
        # logger.warn(f"[解析公众号消息通知] [解析失败] {err}")
        return None
    else:
        mdict = {}
        for child in root:
            if child.tag == "ScanCodeInfo":
                mdict["ScanCodeInfo"] = [item.text for item in child]
            else:
                mdict[child.tag] = child.text
        # logger.info(f"[解析公众号消息通知] [解析成功]{mdict}")
        return mdict


@shared_task
def paresAlert(resont):
    """解析微信公众号消息通知"""
    res = toDict(resont)
    if not res:
        logger.warn(f"[解析公众号消息通知] [解析失败]")
        return
    logger.info(f"[解析公众号消息通知] [解析成功]{res}")
    if res.get("MsgType") == "event":
        user = hasWxUserInfo(res.get("FromUserName"))
        if not user:
            logger.warn(f"[解析公众号消息通知] 查询用户信息失败")
            return

        logger.info(f"[解析公众号消息通知] [用户信息] {user}")
        if (
            res.get("Event") == "view_miniprogram"
            and res.get("appid") == "wx5c86c1ae9599a236"
        ):
            # 菜单点击
            logger.warn(f"[解析公众号消息通知] [菜单事件] {state} ")
        if (
            res.get("Event") == "scancode_push"
            and res.get("EventKey") == "rselfmenu_0_1"
        ):
            # 扫码事件
            url = res.get("ScanCodeInfo")[1]
            idx = url.find("state=")
            state = url[idx + 6 : idx + 6 + 6]
            logger.info(f"[解析公众号消息通知] [State] {state}")
            # 格式化数据
            data = {
                "openid": user.get("openid"),
                "nickname": user.get("nickname", "wx用户"),
                "sex": user.get("sex"),
                "province": user.get("province"),
                "city": user.get("city"),
                "country": user.get("country"),
                "headimgurl": user.get("headimgurl"),
                "privilege": user.get("privilege"),
            }
            logger.info(f"[解析公众号消息通知] [Data] {data}")
            # 保存用户数据
            try:
                unionid = user.get("unionid")
                rrr, faq = AllinpayWxchatList.objects.update_or_create(
                    defaults=data, unionid=unionid
                )
                if faq:
                    rrr.creattime = timezone.now()
                    rrr.status = 1
                    rrr.userid = hasNewUserId(data["nickname"])
                rrr.lasttime = timezone.now()
                rrr.save()
            except BaseException as err:
                logger.warn(f"[解析公众号消息通知] [创建用户失败] {err}")

            # 缓存本地用户Token
            try:
                u = AllinpayUserUserList.objects.get(id=rrr.userid)
            except BaseException as err:
                logger.warn(f"[解析公众号消息通知] [加载用户失败] {err}")
                return {"errcode": 400, "errmsg": "创建token失败"}
            else:
                token = get_tokens_for_user(u)["access"]
                rds = get_redis_connection("sysconf")
                rds.set(state, f"Bearer {token}")
                res = rds.expire(state, 180)
            logger.warn(f"[解析公众号消息通知] [扫码登录剪辑] {state} ")
        if (
            res.get("Event") == "VIEW"
            and res.get("EventKey") == "http://lease.3vppt.com/www/h5media.html"
        ):
            logger.info(f'[解析公众号消息通知] [菜单事件] {res.get("EventKey")} ')
        if res.get("Event") == "subscribe" or res.get("Event") == "SCAN":
            logger.info(f'[解析公众号消息通知] [Event] {res.get("Event")}')
            # 关注或扫码
            state = res.get("EventKey")
            logger.info(f"[解析公众号消息通知] [State] {state}")
            # 格式化数据
            data = {
                "openid": user.get("openid"),
                "nickname": user.get("nickname", "wx用户"),
                "sex": user.get("sex"),
                "province": user.get("province"),
                "city": user.get("city"),
                "country": user.get("country"),
                "headimgurl": user.get("headimgurl"),
                "privilege": user.get("privilege"),
            }
            logger.info(f"[解析公众号消息通知] [Data] {data}")
            # 保存用户数据
            try:
                unionid = user.get("unionid")
                rrr, faq = AllinpayWxchatList.objects.update_or_create(
                    defaults=data, unionid=unionid
                )
                if faq:
                    rrr.creattime = timezone.now()
                    rrr.status = 1
                    rrr.userid = hasNewUserId(data["nickname"])
                rrr.lasttime = timezone.now()
                rrr.save()
            except BaseException as err:
                logger.warn(f"[解析公众号消息通知] [创建用户失败] {err}")

            # 缓存本地用户Token
            try:
                u = AllinpayUserUserList.objects.get(id=rrr.userid)
            except BaseException as err:
                logger.warn(f"[解析公众号消息通知] [加载用户失败] {err}")
                return {"errcode": 400, "errmsg": "创建token失败"}
            else:
                token = get_tokens_for_user(u)["access"]
                rds = get_redis_connection("sysconf")
                rds.set(state, f"Bearer {token}")
                res = rds.expire(state, 180)

            logger.warn(f"[解析公众号消息通知] [扫码关注] {state} ")
        if res.get("Event") == "unsubscribe":
            # 取关
            pass
    else:
        logger.warn(f"[解析公众号消息通知] [未知消息类型] {res} ")

    return {"errcode": 200, "errmsg": "解析微信公众号消息通知!"}


@shared_task
def paresWxPayCrypto(data):
    """解密微信支付回调消息"""
    # logger.info(f"[微信支付回调] [数据类型] {data}")
    try:
        # resont = json.loads(data)
        # resont = eval(data)
        resont = dict(data)
    except BaseException as err:
        logger.error(f"[微信支付回调] [数据格式化失败] {type(data)} {err}")
        return {"errcode": 400, "errmsg": "数据格式化失败"}

    logger.info(f'[微信支付回调] [event_type] {resont.get("event_type")}')
    if (
        resont.get("event_type")
        == "TRANSACTION.SUCCESS"  # 通知的类型，支付成功通知的类型为TRANSACTION.SUCCESS。
        and resont.get("resource_type")
        == "encrypt-resource"  # 通知的资源数据类型，支付成功通知为encrypt-resource。
    ):

        r = resont.get("resource")
        rus = WPC.aes_decrypt(
            r.get("nonce"),
            r.get("ciphertext"),
            r.get("associated_data"),
            WPC.apiv3,
        )
        logger.info(f"[微信支付回调] [充值解密] {type(rus)} {rus}")
        try:
            res = json.loads(rus)
            logger.info(f"[微信支付回调] [支付解密成功] {type(res)} {res} ")
        except BaseException as err:
            logger.error(f"[微信支付回调] [数据解密失败] {err}")
            return {"errcode": 400, "errmsg": "数据解密失败"}

        if res.get("attach") == "ai":
            try:
                te = res["success_time"].split("+")[0].replace("T", " ")
                AllinpayRobotReqsn.objects.filter(reqsn=res["out_trade_no"]).update(
                    appid=res["appid"],
                    mchid=res["mchid"],
                    lasttime=te,
                    total=res["amount"]["payer_total"],  # 支付金额
                    transaction_id=res["transaction_id"],
                    status=1,
                )
                logger.info(f"[微信支付回调] [ai项目] 更新订单成功")
            except BaseException as err:
                logger.warn(f"[微信支付回调] [ai项目] 更新订单失败 {err}")

            # 查询用户数据
            uid = userid_from_wxapp_openid(res["payer"]["openid"])
            args = balance_from_userid(uid)

            try:
                rsn = AllinpayRobotReqsn.objects.get(reqsn=res["out_trade_no"])
                acc = AllinpayRobotAccount(reqsn=res["out_trade_no"])
                acc.save()
            except BaseException as err:
                logger.warn(f"[微信支付回调] [ai项目] 更新用户账户失败 {err}")
            else:
                acc.description = "ai代币充值"
                acc.amoney = 1 - rsn.money  # 动账代币
                acc.bmoney = args[0] + rsn.money  # 累计代币
                acc.atotal = rsn.amount  # 动账法币
                acc.btotal = args[1] + rsn.amount  # 累计法币
                acc.userid = uid
                acc.dbid = settings.ROBOT_MONEY_DEFAULT
                acc.current = timezone.now()
                acc.hmoney = EncryptedData(acc.bmoney)
                acc.htotal = EncryptedData(acc.btotal)
                acc.save()
                logger.info(f"[微信支付回调] [ai项目] 更新用户账户成功")

                # 模板消息
                openid = openid_from_wxappid(res["payer"]["openid"])
                if openid:
                    WX = wxChatRequest()
                    WX.Template_Recharge(
                        openid, [ainicename_from_uid(uid), acc.amoney, acc.bmoney, te]
                    )

        else:
            try:
                YlWxOrders.objects.filter(out_trade_no=res["out_trade_no"]).update(
                    pay_status=1
                )
                logger.info(f"[微信支付回调] [提交php修改] 更新订单完成")
            except BaseException as err:
                logger.warn(f"[微信支付回调] [提交php修改] 更新订单失败")

    if (
        resont.get("event_type") == "REFUND.SUCCESS"
        and resont.get("resource_type") == "encrypt-resource"
    ):
        r = resont.get("resource")
        rus = WPC.aes_decrypt(
            r.get("nonce"),
            r.get("ciphertext"),
            r.get("associated_data"),
            WPC.apiv3,
        )
        logger.info(f"[微信支付回调] [退款解密] {type(rus)} {rus} ")
        try:
            res = json.loads(rus)
            logger.info(f"[微信支付回调] [退款成功解密成功] {type(res)} {res} ")
        except BaseException as err:
            logger.error(f"[微信支付回调] [退款成功解密失败] {err}")
            return {"errcode": 400, "errmsg": "退款成功解密失败"}
        if res.get("reason") == "ai":
            # 更新数据库
            te = res["success_time"].split("+")[0].replace("T", " ")
            AllinpayRobotReqsn.objects.filter(reqsn=res["out_trade_no"]).update(
                status=3
            )
            rs = AllinpayRobotReqsn(
                reqsn=res["out_refund_no"],
                mchid=res["mchid"],
                transaction_id=res["refund_id"],
                lasttime=te,
                description=f'充值退款{res["out_trade_no"]}',
                mark=-1,
                total=res["amount"]["refund"],
                status=1,
            )
            rs.save()
            # 退币
            try:
                rsn = AllinpayRobotReqsn.objects.get(reqsn=res["out_trade_no"])
                acc = AllinpayRobotAccount(reqsn=res["out_refund_no"])
                acc.save()
            except BaseException as err:
                logger.warn(f"[微信支付回调] [ai项目] 更新用户账户失败 {err}")
            else:
                # 查询用户数据
                args = balance_from_userid(rsn.answer)
                acc.description = "ai代币退款"
                acc.amoney = rsn.money  # 动账代币
                acc.bmoney = args[0] - rsn.money  # 累计代币
                acc.atotal = rsn.amount  # 动账法币
                acc.btotal = args[1] - rsn.amount  # 累计法币
                acc.userid = rsn.answer
                acc.dbid = settings.ROBOT_MONEY_DEFAULT
                acc.current = timezone.now()
                acc.hmoney = EncryptedData(acc.bmoney)
                acc.htotal = EncryptedData(acc.btotal)
                acc.save()
                logger.info(f"[微信支付回调] [ai项目] 更新用户账户成功")

    if resont.get("event_type") == "REFUND.ABNORMAL":
        r = resont.get("resource")
        res = WPC.aes_decrypt(
            r.get("nonce"),
            r.get("ciphertext"),
            r.get("associated_data"),
            WPC.apiv3,
        )
        logger.info(f"[微信支付回调] [退款异常] {res}  ")

    if resont.get("event_type") == "REFUND.CLOSED":
        r = resont.get("resource")
        res = WPC.aes_decrypt(
            r.get("nonce"),
            r.get("ciphertext"),
            r.get("associated_data"),
            WPC.apiv3,
        )
        logger.info(f"[微信支付回调] [退款关闭] {res}  ")
        # res = {
        #     "mchid": "1667492159",
        #     "appid": "wx959c3e8ff427eaeb",
        #     "out_trade_no": "MyFgGlZPsgdsYJQ8kC0ycHHCOsxsVyC6",
        #     "transaction_id": "4200002230202406080041732167",
        #     "trade_type": "JSAPI",
        #     "trade_state": "SUCCESS",
        #     "trade_state_desc": "支付成功",
        #     "bank_type": "OTHERS",
        #     "attach": "",
        #     "success_time": "2024-06-08T03:05:35+08:00",
        #     "payer": {"openid": "oTp3w6vqCEyJ4qI6GGkQmG6pr13Y"},
        #     "amount": {
        #         "total": 1,
        #         "payer_total": 1,
        #         "currency": "CNY",
        #         "payer_currency": "CNY",
        #     },
        # }

    return {"errcode": 200, "errmsg": "微信支付回调!"}


# ================================================
@shared_task
def run_test_task():
    """测试计划任务"""
    print("测试计划任务")
    return {"errcode": 200, "errmsg": "测试计划任务!"}


# ====================================================
# 微信小程序部分
# ====================================================
@shared_task
def wxAppLogin(*args, **kwargs):
    """微信小程序登录"""

    def hasNewUserId(name):
        """创建新用户"""
        r = AllinpayUserUserList(nicename=name, status=1)
        r.save()
        return r.id

    SDK = wxAppsRequest()
    login = SDK.codeLogin(kwargs.get("code"))
    if not login:
        logger.warn(f"[小程序用户登录] [请求失败] {login}")
        raise NameError(f"微信小程序登录失败! {kwargs}")

    # try:
    #     login = login.json()
    # except BaseException as err:
    #     logger.warn(f"[小程序用户登录] [请求失败] {login.text}")
    #     raise NameError(f"微信小程序登录失败! {login}")

    if login.get("errcode"):
        logger.warn(f"[小程序用户登录] [返回失败] {login}")
        raise NameError(f"微信小程序登录失败! {login}")
    if not login.get("unionid"):
        raise NameError(f"微信小程序未加入开放平台! {login}")
    logger.info(f"[小程序用户登录] [成功] {login}")

    # 更新用户
    rrr, faq = AllinpayWxchatList.objects.update_or_create(
        unionid=login["unionid"], defaults={"appid": login["openid"]}
    )
    if faq:
        logger.info(f"[小程序] 创建新的微信用户 {rrr.id}")
        uid = hasNewUserId("新用户")
        rrr.creattime = timezone.now()
        rrr.status = 1
        rrr.userid = uid

        # 创建入账(推广计划)
        refr = kwargs.get("refr")  # 参数传入ID
        if refr:
            from rebot.tasks import has_chocode, User_account_deduction

            reqsn = (
                f'PG{time.strftime("%Y%m%d%H%M%S", time.localtime())}{has_chocode(6)}'
            )
            licre = 0 - settings.RPBOT_MONEY_SPREAD
            repay = User_account_deduction(
                **{
                    "reqsn": f"{reqsn}",
                    "description": f"小程序推广计划奖励 {rrr.userid}",
                    "amoney": licre,
                    "atotal": 0,
                    "userid": refr,
                    "dbid": settings.ROBOT_MONEY_DEFAULT,
                    "current": timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
                }
            )
            if not repay == "success":
                raise BaseException("奖励失败")

    rrr.lasttime = timezone.now()
    rrr.save()

    # 本地用户T
    try:
        u = AllinpayUserUserList.objects.get(id=rrr.userid)
    except BaseException as err:
        logger.warn(f"[小程序用户登录] [创建token失败] {err}")
        raise NameError(f"创建token失败! {login}")

    # 虚拟好友
    try:
        AllinpayRobotList.objects.update_or_create(
            defaults={"lasttime": timezone.now()}, isbot=0, parent=u.id
        )
    except BaseException as err:
        logger.warn(f"[小程序用户登录] [创建虚拟好友失败] {err}")
        raise NameError(f"创建token失败! {login}")

    # 创建Token
    token = get_tokens_for_user(u)["access"]
    return {
        "errcode": 200,
        "errmsg": f"Bearer {token}",
        "session": login["session_key"],
        "uid": u.id,
    }


# ====================================================
# 微信公众号模板消息
# ====================================================
@shared_task
def WeAppsOpenidformSend(touser, path, modelname, sargs):
    """公众号模板消息\n touser str openid,\n path str url,\n modelname str model_name,\n sargs List *args"""
    # 实例化小程序api对象
    WX = wxChatRequest()

    # url = "/cgi-bin/message/wxopen/template/uniform_send"
    url = "/cgi-bin/message/template/send"
    headers = {"Content-Type": "application/json"}
    params = {"access_token": WX.hasToken()}

    # 创建参数
    # path="https://we.hs6686.com/#/lol"
    tmpl = toMppsModelMessage(modelname)
    if not tmpl:
        logger.warn(f"[WeMessage] 查询模板失败")
        raise ValueError(f"[WeMessage] 查询模板失败")

    args = [{"value": s} for s in sargs]
    if not len(tmpl["fields"]) == len(args):
        logger.warn(f"[WeMessage] 参数不匹配")
        raise ValueError(f"[WeMessage] 参数不匹配")
    data = {
        "touser": touser,
        "template_id": tmpl.get("mid"),
        "url": path,
        #    "miniprogram":{
        #      "appid":WX.Conf.get("appid"),
        #      "pagepath":path,
        #    },
        #    "client_msg_id":"MSG_000001",
        "data": dict(zip(tmpl["fields"], args)),
    }
    # print("请求数据:", params, data)
    r = WX.do_post(
        url,
        headers=headers,
        params=params,
        data=json.dumps(data),
    )
    logger.info(f"[公众号模板消息] [{touser}] [{modelname}] {sargs} {r}")
    return r if r else {"errcode": 999, "errmsg": "send error"}
